home *** CD-ROM | disk | FTP | other *** search
/ Clickx 115 / Clickx 115.iso / software / tools / windows / tails-i386-0.16.iso / live / filesystem.squashfs / usr / share / pyshared / invest / quotes.py < prev    next >
Encoding:
Python Source  |  2010-07-07  |  8.6 KB  |  258 lines

  1. from os.path import join
  2. import gnomeapplet, gtk, gtk.gdk, gconf, gobject
  3. from gettext import gettext as _
  4. import csv
  5. from urllib import urlopen
  6. import datetime
  7. from threading import Thread
  8.  
  9. import invest, invest.about, invest.chart
  10.  
  11. CHUNK_SIZE = 512*1024 # 512 kB
  12. AUTOREFRESH_TIMEOUT = 15*60*1000 # 15 minutes
  13.  
  14. QUOTES_URL="http://finance.yahoo.com/d/quotes.csv?s=%(s)s&f=sl1d1t1c1ohgv&e=.csv"
  15.  
  16. # Sample (25/4/2008): UCG.MI,"4,86",09:37:00,2008/04/25,"0,07","4,82","4,87","4,82",11192336
  17. QUOTES_CSV_FIELDS=["ticker", ("trade", float), "time", "date", ("variation", float), ("open", float)]
  18.  
  19. # based on http://www.johnstowers.co.nz/blog/index.php/2007/03/12/threading-and-pygtk/
  20. class _IdleObject(gobject.GObject):
  21.     """
  22.     Override gobject.GObject to always emit signals in the main thread
  23.     by emmitting on an idle handler
  24.     """
  25.     def __init__(self):
  26.         gobject.GObject.__init__(self)
  27.  
  28.     def emit(self, *args):
  29.         gobject.idle_add(gobject.GObject.emit,self,*args)
  30.  
  31. class QuotesRetriever(Thread, _IdleObject):
  32.     """
  33.     Thread which uses gobject signals to return information
  34.     to the GUI.
  35.     """
  36.     __gsignals__ =  {
  37.             "completed": (
  38.                 gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []),
  39.             # FIXME: We don't monitor progress, yet ...
  40.             #"progress": (
  41.             #    gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [
  42.             #    gobject.TYPE_FLOAT])        #percent complete
  43.             }
  44.  
  45.     def __init__(self, tickers):
  46.         Thread.__init__(self)
  47.         _IdleObject.__init__(self)
  48.         self.tickers = tickers
  49.         self.retrieved = False
  50.         self.data = []
  51.  
  52.     def run(self):
  53.         quotes_url = QUOTES_URL % {"s": self.tickers}
  54.         try:
  55.             quotes_file = urlopen(quotes_url, proxies = invest.PROXY)
  56.             self.data = quotes_file.readlines ()
  57.             quotes_file.close ()
  58.         except Exception, msg:
  59.             invest.debug("Error while retrieving quotes data (url = %s): %s" % (quotes_url, msg))
  60.         else:
  61.             self.retrieved = True
  62.         self.emit("completed")
  63.  
  64.  
  65. class QuoteUpdater(gtk.ListStore):
  66.     updated = False
  67.     last_updated = None
  68.     quotes_valid = False
  69.     timeout_id = None
  70.     SYMBOL, LABEL, TICKER_ONLY, BALANCE, BALANCE_PCT, VALUE, VARIATION_PCT, PB = range(8)
  71.     def __init__ (self, change_icon_callback, set_tooltip_callback):
  72.         gtk.ListStore.__init__ (self, gobject.TYPE_STRING, gobject.TYPE_STRING, bool, float, float, float, float, gtk.gdk.Pixbuf)
  73.         self.set_update_interval(AUTOREFRESH_TIMEOUT)
  74.         self.change_icon_callback = change_icon_callback
  75.         self.set_tooltip_callback = set_tooltip_callback
  76.         self.set_sort_column_id(1, gtk.SORT_ASCENDING)
  77.         self.refresh()
  78.  
  79.         # tell the network manager to notify me when network status changes
  80.         invest.nm.set_statechange_callback(self.nm_state_changed)
  81.  
  82.     def set_update_interval(self, interval):
  83.         if self.timeout_id != None:
  84.             invest.debug("Canceling refresh timer")
  85.             gobject.source_remove(self.timeout_id)
  86.             self.timeout_id = None
  87.         if interval > 0:
  88.             invest.debug("Setting refresh timer to %s:%02d.%03d" % ( interval / 60000, interval % 60000 / 1000, interval % 1000) )
  89.             self.timeout_id = gobject.timeout_add(interval, self.refresh)
  90.  
  91.     def nm_state_changed(self):
  92.         # when nm is online but we do not have an update timer, create it and refresh
  93.         if invest.nm.online():
  94.             if self.timeout_id == None:
  95.                 self.set_update_interval(AUTOREFRESH_TIMEOUT)
  96.                 self.refresh()
  97.  
  98.     def refresh(self):
  99.         invest.debug("Refreshing")
  100.  
  101.         # when nm tells me I am offline, stop the update interval
  102.         if invest.nm.offline():
  103.             invest.debug("We are offline, stopping update timer")
  104.             self.set_update_interval(0)
  105.             return False
  106.  
  107.         if len(invest.STOCKS) == 0:
  108.             return True
  109.  
  110.         tickers = '+'.join(invest.STOCKS.keys())
  111.         quotes_retriever = QuotesRetriever(tickers)
  112.         quotes_retriever.connect("completed", self.on_retriever_completed)
  113.         quotes_retriever.start()
  114.  
  115.         return True
  116.  
  117.  
  118.     def on_retriever_completed(self, retriever):
  119.         if retriever.retrieved == False:
  120.             tooltip = [_('Invest could not connect to Yahoo! Finance')]
  121.             if self.last_updated != None:
  122.                 tooltip.append(_('Updated at %s') % self.last_updated.strftime("%H:%M"))
  123.             self.set_tooltip_callback('\n'.join(tooltip))
  124.         else:
  125.             self.populate(self.parse_yahoo_csv(csv.reader(retriever.data)))
  126.             self.updated = True
  127.             self.last_updated = datetime.datetime.now()
  128.             tooltip = []
  129.             if self.simple_quotes_count > 0:
  130.                 # Translators: This is share-market jargon. It is the percentage change in the price of a stock. The %% gets changed to a single percent sign and the %+.2f gets replaced with the value of the change.
  131.                 tooltip.append(_('Quotes average change %%: %+.2f%%') % self.avg_simple_quotes_change)
  132.             if self.positions_count > 0:
  133.                 # Translators: This is share-market jargon. It refers to the total difference between the current price and purchase price for all the shares put together. i.e. How much money would be earned if they were sold right now.
  134.                 tooltip.append(_('Positions balance: %+.2f') % self.positions_balance)
  135.             tooltip.append(_('Updated at %s') % self.last_updated.strftime("%H:%M"))
  136.             self.set_tooltip_callback('\n'.join(tooltip))
  137.  
  138.  
  139.  
  140.     def parse_yahoo_csv(self, csvreader):
  141.         result = {}
  142.         for fields in csvreader:
  143.             if len(fields) == 0:
  144.                 continue
  145.  
  146.             result[fields[0]] = {}
  147.             for i, field in enumerate(QUOTES_CSV_FIELDS):
  148.                 if type(field) == tuple:
  149.                     try:
  150.                         result[fields[0]][field[0]] = field[1](fields[i])
  151.                     except:
  152.                         result[fields[0]][field[0]] = 0
  153.                 else:
  154.                     result[fields[0]][field] = fields[i]
  155.             # calculated fields
  156.             try:
  157.                 result[fields[0]]['variation_pct'] = result[fields[0]]['variation'] / float(result[fields[0]]['trade'] - result[fields[0]]['variation']) * 100
  158.             except ZeroDivisionError:
  159.                 result[fields[0]]['variation_pct'] = 0
  160.         return result
  161.  
  162.     def populate(self, quotes):
  163.         if (len(quotes) == 0):
  164.             return
  165.  
  166.         self.clear()
  167.         
  168.         try:
  169.             quote_items = quotes.items ()
  170.             quote_items.sort ()
  171.  
  172.             simple_quotes_change = 0
  173.             self.simple_quotes_count = 0
  174.             self.positions_balance = 0
  175.             self.positions_count = 0
  176.  
  177.             for ticker, val in quote_items:
  178.                 pb = None
  179.  
  180.                 # get the label of this stock for later reuse
  181.                 label = invest.STOCKS[ticker]["label"]
  182.                 if len(label) == 0:
  183.                     label = ticker
  184.  
  185.                 # Check whether the symbol is a simple quote, or a portfolio value
  186.                 is_simple_quote = True
  187.                 for purchase in invest.STOCKS[ticker]["purchases"]:
  188.                     if purchase["amount"] != 0:
  189.                         is_simple_quote = False
  190.                         break
  191.  
  192.                 if is_simple_quote:
  193.                     self.simple_quotes_count += 1
  194.                     row = self.insert(0, [ticker, label, True, 0, 0, val["trade"], val["variation_pct"], pb])
  195.                     simple_quotes_change += val['variation_pct']
  196.                 else:
  197.                     self.positions_count += 1
  198.                     current = sum([purchase["amount"]*val["trade"] for purchase in invest.STOCKS[ticker]["purchases"] if purchase["amount"] != 0])
  199.                     paid = sum([purchase["amount"]*purchase["bought"] + purchase["comission"] for purchase in invest.STOCKS[ticker]["purchases"] if purchase["amount"] != 0])
  200.                     balance = current - paid
  201.                     if paid != 0:
  202.                         change = 100*balance/paid
  203.                     else:
  204.                         change = 100 # Not technically correct, but it will look more intuitive than the real result of infinity.
  205.                     row = self.insert(0, [ticker, label, False, balance, change, val["trade"], val["variation_pct"], pb])
  206.                     self.positions_balance += balance
  207.  
  208.                 if len(ticker.split('.')) == 2:
  209.                     url = 'http://ichart.europe.yahoo.com/h?s=%s' % ticker
  210.                 else:
  211.                     url = 'http://ichart.yahoo.com/h?s=%s' % ticker
  212.  
  213.                 image_retriever = invest.chart.ImageRetriever(url)
  214.                 image_retriever.connect("completed", self.set_pb_callback, row)
  215.                 image_retriever.start()
  216.  
  217.             if self.simple_quotes_count > 0:
  218.                 self.avg_simple_quotes_change = simple_quotes_change/float(self.simple_quotes_count)
  219.             else:
  220.                 self.avg_simple_quotes_change = 0
  221.  
  222.             if self.avg_simple_quotes_change != 0:
  223.                 simple_quotes_change_sign = self.avg_simple_quotes_change / abs(self.avg_simple_quotes_change)
  224.             else:
  225.                 simple_quotes_change_sign = 0
  226.  
  227.             # change icon
  228.             if self.simple_quotes_count > 0:
  229.                 self.change_icon_callback(simple_quotes_change_sign)
  230.             else:
  231.                 positions_balance_sign = self.positions_balance/abs(self.positions_balance)
  232.                 self.change_icon_callback(positions_balance_sign)
  233.                 
  234.             # mark quotes to finally be valid
  235.             self.quotes_valid = True
  236.  
  237.         except Exception, msg:
  238.             invest.debug("Failed to populate quotes: %s" % msg)
  239.             invest.debug(quotes)
  240.             self.quotes_valid = False
  241.  
  242.     def set_pb_callback(self, retriever, row):
  243.         self.set_value(row, self.PB, retriever.image.get_pixbuf())
  244.  
  245.     # check if we have only simple quotes
  246.     def simple_quotes_only(self):
  247.         res = True
  248.         for entry, data in invest.STOCKS.iteritems():
  249.             purchases = data["purchases"]
  250.             for purchase in purchases:
  251.                 if purchase["amount"] != 0:
  252.                     res = False
  253.                     break
  254.         return res
  255.  
  256. if gtk.pygtk_version < (2,8,0):
  257.     gobject.type_register(QuoteUpdater)
  258.